﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using System.Web.WebPages;

namespace Share.Bonnet.Pager {
    public static class Pager {

        // Constants
        public const string CSS_CLASS_PAGER = "pager";
        public const string CSS_CLASS_PAGINATION = "pagination";
        public const string CSS_CLASS_DISABLED = "disabled";
        public const string CSS_CLASS_ACTIVE = "active";
        public const string CSS_CLASS_FIRST = "first";
        public const string CSS_CLASS_PREVIOUS = "previous";
        public const string CSS_CLASS_NEXT = "next";
        public const string CSS_CLASS_LAST = "last";

        public const string TEXT_PREVIOUS = "&laquo;";
        public const string TEXT_NEXT = "&raquo;";

        /// <summary>
        /// Renders a pager based on information obtained from IPagerInfo
        /// 
        /// Default behaviour includes:
        /// - Sticky first and last page (only rendered if not among pages)
        /// - Previous and next page (disabled if no previous or next page exists)
        /// - Uses a series of Func<>'s to generate html
        /// - Fully bootstrap compatible
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="data"></param>
        /// <param name="GetPageLink"></param>
        /// <param name="GetFirstPageLink"></param>
        /// <param name="GetPreviousPageLink"></param>
        /// <param name="GetNextPageLink"></param>
        /// <param name="GetLastPageLink"></param>
        /// <param name="BuilderUL"></param>
        /// <param name="BuilderLI"></param>
        /// <returns></returns>
        public static MvcHtmlString RenderPager(this HtmlHelper helper, IPagerData data,
            Func<int, string, MvcHtmlString> GetPageLink,
            TagBuilder BuilderUL,
            TagBuilder BuilderLI,
            int MaxPages,
            Func<int, string, MvcHtmlString> GetFirstPageLink = null,
            Func<int, string, MvcHtmlString> GetPreviousPageLink = null,
            Func<int, string, MvcHtmlString> GetNextPageLink = null,
            Func<int, string, MvcHtmlString> GetLastPageLink = null) {

            // Calculate total pages
            int pageCount = (int)Math.Ceiling(
                (double)data.Total / data.PerPage);

            // Ajust MaxPages
            MaxPages = Math.Min(MaxPages, pageCount);

            // Calculate pages before and after current
            List<int> pagesBeforeCurrent = new List<int>();
            List<int> pagesAfterCurrent = new List<int>();

            for (int i = 1; i < MaxPages; i++) {
                int page;
                if ((pagesAfterCurrent.Count > pagesBeforeCurrent.Count || data.Current == pageCount || 
                    (pagesAfterCurrent.Count > 0 && pagesAfterCurrent.Last() == pageCount)) && 
                    (data.Current > 1 && (pagesBeforeCurrent.Count == 0 || pagesBeforeCurrent.Last() > 1))) {

                    // Add before current
                    if (pagesBeforeCurrent.Count == 0) {
                        page = data.Current;
                    } else {
                        page = pagesBeforeCurrent.Last();
                    }
                    pagesBeforeCurrent.Add(--page);
                } else { 
                    // Add after current
                    if (pagesAfterCurrent.Count == 0) {
                        page = data.Current;
                    } else {
                        page = pagesAfterCurrent.Last();
                    }
                    pagesAfterCurrent.Add(++page);
                }
            }

            // Add Previous page
            if (null != GetPreviousPageLink) {
                int previousPage;
                if (data.Current > 1) {
                    previousPage = data.Current - 1;
                } else {
                    previousPage = 1;
                }

                if (data.Current == 1) {
                    BuilderLI.AddCssClass(CSS_CLASS_DISABLED);
                }

                BuilderLI.AddCssClass(CSS_CLASS_PREVIOUS);
                BuilderLI.InnerHtml = GetPreviousPageLink(previousPage, TEXT_PREVIOUS).ToString();
                BuilderUL.InnerHtml += BuilderLI.ToString();
                BuilderLI.Attributes["class"] = String.Empty;
            }

            if (pagesBeforeCurrent.Count() > 0) {
                // Reverse the pages before current
                pagesBeforeCurrent.Reverse();
            }

            // Add First page
            if (null != GetFirstPageLink && pagesBeforeCurrent.Count() > 0 && !pagesBeforeCurrent.Contains(1)) {
                // Canibalize the smallest page before current
                pagesBeforeCurrent.Remove(pagesBeforeCurrent.First());

                BuilderLI.AddCssClass(CSS_CLASS_FIRST);
                BuilderLI.InnerHtml = GetFirstPageLink(1, 1.ToString()).ToString();
                BuilderUL.InnerHtml += BuilderLI.ToString();
                BuilderLI.Attributes["class"] = String.Empty;
            }

            // Add Pages before current
            foreach(int i in pagesBeforeCurrent) {
                BuilderLI.InnerHtml = GetPageLink(i, i.ToString()).ToString();
                BuilderUL.InnerHtml += BuilderLI.ToString();
                BuilderLI.Attributes["class"] = String.Empty;
            }

            // Add Current page
            BuilderLI.AddCssClass(CSS_CLASS_ACTIVE);
            BuilderLI.InnerHtml = GetPageLink(data.Current, data.Current.ToString()).ToString();
            BuilderUL.InnerHtml += BuilderLI.ToString();
            BuilderLI.Attributes["class"] = String.Empty;

            if (pagesAfterCurrent.Count() > 0 && !pagesAfterCurrent.Contains(pageCount)) {
                // Canibalize the largest page after current
                pagesAfterCurrent.Remove(pagesAfterCurrent.Last());
            }

            // Add Pages after current
            foreach(int i in pagesAfterCurrent) {
                BuilderLI.InnerHtml = GetPageLink(i, i.ToString()).ToString();
                BuilderUL.InnerHtml += BuilderLI.ToString();
                BuilderLI.Attributes["class"] = String.Empty;
            }

            // Add Last page
            if (null != GetLastPageLink && pagesAfterCurrent.Count() > 0 && !pagesAfterCurrent.Contains(pageCount)) {
                BuilderLI.AddCssClass(CSS_CLASS_LAST);
                BuilderLI.InnerHtml = GetFirstPageLink(pageCount, pageCount.ToString()).ToString();
                BuilderUL.InnerHtml += BuilderLI.ToString();
                BuilderLI.Attributes["class"] = String.Empty;
            }

            // Add Next page
            if (null != GetLastPageLink) {
                int nextPage;
                if (data.Current < pageCount) {
                    nextPage = data.Current + 1;
                } else {
                    nextPage = pageCount;
                }

                if (data.Current == pageCount) {
                    BuilderLI.AddCssClass(CSS_CLASS_DISABLED);
                }

                BuilderLI.AddCssClass(CSS_CLASS_NEXT);
                BuilderLI.InnerHtml = GetNextPageLink(nextPage, TEXT_NEXT).ToString();
                BuilderUL.InnerHtml += BuilderLI.ToString();
                BuilderLI.Attributes["class"] = String.Empty;
            }

            return new MvcHtmlString(BuilderUL.ToString());
        }

        /// <summary>
        /// Renders a pager by using the GetPageLink Func for each
        /// function required
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="data"></param>
        /// <param name="GetPageLink"></param>
        /// <param name="BuilderUL"></param>
        /// <param name="MaxPages"></param>
        /// <returns></returns>
        public static MvcHtmlString RenderPager(this HtmlHelper helper, IPagerData data, Func<int, string, MvcHtmlString> GetPageLink, TagBuilder BuilderUL, int MaxPages = 10) {
            return helper.RenderPager(data, GetPageLink, BuilderUL, new TagBuilder("li"), MaxPages, GetPageLink, GetPageLink, GetPageLink, GetPageLink);
        }

        /// <summary>
        /// Renders a pager with a pre confirured tagbuilder for the 
        /// ul which includes a 'pager' css class (used by bootstrap)
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="data"></param>
        /// <param name="GetPageLink"></param>
        /// <param name="MaxPages"></param>
        /// <returns></returns>
        public static MvcHtmlString RenderPager(this HtmlHelper helper, IPagerData data, Func<int, string, MvcHtmlString> GetPageLink, int MaxPages = 10) {
            TagBuilder BuilderUL;
            BuilderUL = new TagBuilder("ul");
            BuilderUL.AddCssClass(CSS_CLASS_PAGER);

            return helper.RenderPager(data, GetPageLink, BuilderUL, MaxPages);
        }

        /// <summary>
        /// Renders a pager with a pre confirured tagbuilder for the 
        /// ul which includes a 'pagination' css class (used by bootstrap)
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="data"></param>
        /// <param name="GetPageLink"></param>
        /// <param name="MaxPages"></param>
        /// <returns></returns>
        public static MvcHtmlString RenderPagination(this HtmlHelper helper, IPagerData data, Func<int, string, MvcHtmlString> GetPageLink, int MaxPages = 10) {
            TagBuilder BuilderUL;
            BuilderUL = new TagBuilder("ul");
            BuilderUL.AddCssClass(CSS_CLASS_PAGINATION);

            return helper.RenderPager(data, GetPageLink, BuilderUL, MaxPages);
        }
    }
}
